﻿using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using DaveSexton.Labs.Properties;

namespace DaveSexton.Labs
{
	/// <summary>
	/// Represents a <see cref="TraceSource"/> for a <see cref="Lab"/>.
	/// </summary>
	public class LabTraceSource : TraceSource
	{
		#region Public Properties
		/// <summary>
		/// The default singleton <see cref="LabTraceSource"/>.
		/// </summary>
		[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Mutability is acceptable.")]
		public static readonly LabTraceSource Default = new LabTraceSource("DaveSexton.Labs");
		#endregion

		#region Private / Protected
		private readonly Stopwatch watch = Stopwatch.StartNew();
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="LabTraceSource" /> class for derived classes.
		/// </summary>
		protected LabTraceSource(string name)
#if !SILVERLIGHT && !NETFX_CORE
			: base(name, SourceLevels.All)
#else
			: base(name)
#endif
		{
			Contract.Requires(!string.IsNullOrEmpty(name));

#if !SILVERLIGHT || WINDOWS_PHONE
			Contract.Assume(watch != null);
#endif
		}

#if !SILVERLIGHT && !NETFX_CORE
		/// <summary>
		/// Constructs a new instance of the <see cref="LabTraceSource" /> class for derived classes.
		/// </summary>
		protected LabTraceSource(string name, SourceLevels defaultLevel)
			: base(name, defaultLevel)
		{
			Contract.Requires(!string.IsNullOrEmpty(name));
			Contract.Requires(Enum.IsDefined(typeof(SourceLevels), defaultLevel));

#if !SILVERLIGHT || WINDOWS_PHONE
			Contract.Assume(watch != null);
#endif
		}
#endif
		#endregion

		#region Methods
		[ContractInvariantMethod]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
		private void ObjectInvariant()
		{
			Contract.Invariant(watch != null);
		}

		/// <summary>
		/// Formats the specified <paramref name="message"/> with the specified <paramref name="args"/>.
		/// </summary>
		/// <param name="message">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		/// <returns>A formatted <paramref name="message"/> with the specified <paramref name="args"/>.</returns>
		public static string Format(string message, params object[] args)
		{
			Contract.Ensures(Contract.Result<string>() != null);

			if (message == null)
			{
				return string.Empty;
			}
			else if (args == null)
			{
				return message;
			}
			else
			{
				return string.Format(CultureInfo.CurrentCulture, message, args);
			}
		}

		#region Tracing
		/// <summary>
		/// Sends the specified message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="format">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		public void Trace(string format, params object[] args)
		{
			Trace(Format(format, args));
		}

		/// <summary>
		/// Sends the specified <paramref name="value"/> to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="value">The object to be sent.</param>
		public void Trace(object value)
		{
			Trace(value == null ? string.Empty : value.ToString());
		}

		/// <summary>
		/// Sends the specified message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="message">The message to be sent.</param>
		public void Trace(string message)
		{
			TraceSourceExtensions.Trace(this, message);
		}

		/// <summary>
		/// Sends a new line to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		public void TraceLine()
		{
			TraceLine(string.Empty);
		}

		/// <summary>
		/// Sends the specified message to all <see cref="TraceSource.Listeners"/> and starts a new line.
		/// </summary>
		/// <param name="format">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		public void TraceLine(string format, params object[] args)
		{
			TraceLine(Format(format, args));
		}

		/// <summary>
		/// Sends the specified <paramref name="value"/> to all <see cref="TraceSource.Listeners"/> and starts a new line.
		/// </summary>
		/// <param name="value">The object to be sent.</param>
		public void TraceLine(object value)
		{
			TraceLine(value == null ? string.Empty : value.ToString());
		}

		/// <summary>
		/// Sends the specified message to all <see cref="TraceSource.Listeners"/> and starts a new line.
		/// </summary>
		/// <param name="message">The message to be sent.</param>
		public void TraceLine(string message)
		{
			TraceSourceExtensions.TraceLine(this, message);
		}
		#endregion

		#region Semantic Tracing
		/// <summary>
		/// Sends the specified description to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="description">The message to be sent.</param>
		public virtual void TraceDescription(string description)
		{
			TraceLine(description);
			TraceLine();
		}

		/// <summary>
		/// Sends the specified success message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="format">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		public void TraceSuccess(string format, params object[] args)
		{
			TraceSuccess(Format(format, args));
		}

		/// <summary>
		/// Sends the specified success message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="message">The message to be sent.</param>
		public void TraceSuccess(string message)
		{
			TraceSourceExtensions.TraceStart(this, message);
		}

		/// <summary>
		/// Sends a success message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		public void TraceSuccess()
		{
			TraceSuccess(TraceSourceText.Success);
		}

		/// <summary>
		/// Sends the specified failure message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="format">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		public void TraceFailure(string format, params object[] args)
		{
			TraceFailure(Format(format, args));
		}

		/// <summary>
		/// Sends the specified failure message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="message">The message to be sent.</param>
		public void TraceFailure(string message)
		{
			TraceSourceExtensions.TraceStop(this, message);
		}

		/// <summary>
		/// Sends a failure message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		public void TraceFailure()
		{
			TraceFailure(TraceSourceText.Failure);
		}

		/// <summary>
		/// Sends the specified status message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="format">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		public void TraceStatus(string format, params object[] args)
		{
			TraceStatus(Format(format, args));
		}

		/// <summary>
		/// Sends the specified status message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="message">The message to be sent.</param>
		public void TraceStatus(string message)
		{
			TraceEvent(TraceEventType.Transfer, message);
		}

		/// <summary>
		/// Sends the specified title message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="format">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		public void TraceTitle(string format, params object[] args)
		{
			TraceTitle(Format(format, args));
		}

		/// <summary>
		/// Sends the specified title message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="message">The message to be sent.</param>
		public void TraceTitle(string message)
		{
			TraceSourceExtensions.TraceVerbose(this, message);
		}
		#endregion

		#region Event Tracing
		/// <summary>
		/// Sends the specified event message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="eventType">The type of the event.</param>
		/// <param name="format">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		public void TraceEvent(TraceEventType eventType, string format, params object[] args)
		{
			TraceSourceExtensions.TraceEvent(this, eventType, format, args);
		}

		/// <summary>
		/// Sends the specified event message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="eventType">The type of the event.</param>
		/// <param name="message">The message to be sent.</param>
		public void TraceEvent(TraceEventType eventType, string message)
		{
			TraceSourceExtensions.TraceEvent(this, eventType, message);
		}

		/// <summary>
		/// Sends the specified <paramref name="exception"/> to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="exception">The <see cref="Exception"/> to be sent.</param>
		public void TraceError(Exception exception)
		{
			TraceSourceExtensions.TraceError(this, exception == null ? TraceSourceText.UnknownException : exception.ToString());
		}

#if !SILVERLIGHT && !NETFX_CORE
		/// <summary>
		/// When error tracing is enabled, invokes the specified <paramref name="message"/> function and traces the result.
		/// </summary>
		/// <remarks>
		/// This overload is useful for when it's expensive to create trace messages.  By passing in a function instead, 
		/// the configured trace level may be consulted first to see if it allows messages to be written at the specified 
		/// level.  Only if the mesage can be written will the function be invoked.
		/// </remarks>
		public void TraceError(Func<string> messageFactory)
		{
			Contract.Requires(messageFactory != null);

			TraceSourceExtensions.TraceError(this, messageFactory);
		}
#endif

		/// <summary>
		/// Sends the specified error message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="message">The message to be sent.</param>
		public void TraceError(string message)
		{
			TraceSourceExtensions.TraceError(this, message);
		}

		/// <summary>
		/// Sends the specified error message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="format">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		public void TraceError(string format, params object[] args)
		{
			TraceSourceExtensions.TraceError(this, format, args);
		}

#if !SILVERLIGHT && !NETFX_CORE
		/// <summary>
		/// When warning tracing is enabled, invokes the specified <paramref name="message"/> function and traces the result.
		/// </summary>
		/// <remarks>
		/// This overload is useful for when it's expensive to create trace messages.  By passing in a function instead, 
		/// the configured trace level may be consulted first to see if it allows messages to be written at the specified 
		/// level.  Only if the mesage can be written will the function be invoked.
		/// </remarks>
		public void TraceWarning(Func<string> messageFactory)
		{
			Contract.Requires(messageFactory != null);

			TraceSourceExtensions.TraceWarning(this, messageFactory);
		}
#endif

		/// <summary>
		/// Sends the specified warning message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="message">The message to be sent.</param>
		public void TraceWarning(string message)
		{
			TraceSourceExtensions.TraceWarning(this, message);
		}

		/// <summary>
		/// Sends the specified warning message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="format">The message to be formatted.</param>
		/// <param name="args">The format arguments.</param>
		public void TraceWarning(string format, params object[] args)
		{
			TraceSourceExtensions.TraceWarning(this, format, args);
		}

#if !SILVERLIGHT && !NETFX_CORE
		/// <summary>
		/// When information tracing is enabled, invokes the specified <paramref name="messageFactory"/> function and traces the result.
		/// </summary>
		/// <remarks>
		/// This overload is useful for when it's expensive to create trace messages.  By passing in a function instead, 
		/// the configured trace level may be consulted first to see if it allows messages to be written at the specified 
		/// level.  Only if the mesage can be written will the function be invoked.
		/// </remarks>
		public void TraceInformation(Func<string> messageFactory)
		{
			Contract.Requires(messageFactory != null);

			TraceSourceExtensions.TraceInformation(this, messageFactory);
		}
#endif
		#endregion

		#region Time Tracing
		/// <summary>
		/// Sends the specified message to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		/// <param name="format">The message to be formatted.  The current time is passed as the first format argument, referenced by <strong>{0}</strong>.</param>
		/// <param name="additionalArgs">Any additional format arguments, referenced in order by <strong>{1}, {2}, {3},...</strong>.</param>
		public void TraceTime(string format, params object[] additionalArgs)
		{
			if (additionalArgs == null || additionalArgs.Length == 0)
			{
				TraceLine(format, watch.Elapsed);
			}
			else
			{
				var args = new object[additionalArgs.Length + 1];

				args[0] = watch.Elapsed;

				Array.Copy(additionalArgs, 0, args, 1, additionalArgs.Length);

				TraceLine(format, args);
			}
		}

		/// <summary>
		/// Sends the current time to all <see cref="TraceSource.Listeners"/>.
		/// </summary>
		public void TraceTime()
		{
			TraceLine(watch.Elapsed);
		}

		/// <summary>
		/// Restarts the timer used by <see cref="TraceTime()"/> and <see cref="TraceTime(string,object[])"/>.
		/// </summary>
		public void ResetTime()
		{
			watch.Restart();
		}
		#endregion
		#endregion
	}
}